Skip to content

feat(generator): add debug_redact to key fields and enable redact in DebugString - #16398

Open
colinmoy wants to merge 5 commits into
googleapis:mainfrom
colinmoy:feat-generator-debug-redact-keys
Open

feat(generator): add debug_redact to key fields and enable redact in DebugString#16398
colinmoy wants to merge 5 commits into
googleapis:mainfrom
colinmoy:feat-generator-debug-redact-keys

Conversation

@colinmoy

@colinmoy colinmoy commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Fixes: b/510919426

@colinmoy
colinmoy requested a review from a team as a code owner August 27, 2026 19:48

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the discovery-to-proto generator to support mapping 'any' types to google.protobuf.Value or google.protobuf.Struct instead of always defaulting to google.protobuf.Any. It also introduces automatic redaction (debug_redact = true) for string/bytes fields containing 'key' in their name and updates DebugString to respect this option. The review feedback highlights several potential nlohmann::json::type_error exceptions that could occur if JSON Schema elements (like additionalProperties, items, or format) are not objects or strings as expected, recommending explicit type checks. Additionally, it suggests refining the redaction logic with a word-boundary check to prevent false positives on fields containing 'key' as a substring (e.g., 'monkey'), utilizing std::string_view per the repository style guide.

I am having trouble creating individual review comments. Click here to see my feedback.

generator/internal/discovery_to_proto.cc (384-395)

high

In JSON Schema, additionalProperties can be a boolean (e.g., false or true). If items["additionalProperties"] is a boolean, calling .value("type", "") or .contains("format") on it will throw a nlohmann::json::type_error exception (specifically type_error.306 because the JSON value is not an object). We should verify that items["additionalProperties"] is an object before calling these methods.

    if (IsDiscoveryArrayType(*current)) {
      auto const& items = (*current)["items"];
      if (items.is_object() &&
          items.contains("type") && items["type"] == "object" &&
          items.contains("additionalProperties") &&
          items["additionalProperties"].is_object() &&
          items["additionalProperties"].value("type", "") == "any" &&
          !items.contains("format") &&
          !items["additionalProperties"].contains("format")) {
        types_to_import.insert("google.protobuf.Struct");
      } else {
        worklist.push_back(&items);
      }
    }

generator/internal/discovery_to_proto.cc (397-407)

high

Similar to the array items check, additional_properties can be a boolean value in JSON Schema. If it is not an object, calling .contains() on it will throw a nlohmann::json::type_error exception. We should ensure additional_properties is an object before querying its properties.

    if (IsDiscoveryMapType(*current)) {
      auto const& additional_properties = (*current)["additionalProperties"];
      if (additional_properties.is_object() &&
          additional_properties.contains("type") &&
          additional_properties["type"] == "any" &&
          !additional_properties.contains("format") &&
          !current->contains("format")) {
        types_to_import.insert("google.protobuf.Struct");
      } else {
        worklist.push_back(&additional_properties);
      }
    }

generator/internal/discovery_to_proto.cc (363-372)

medium

When checking the "format" field, we should ensure it is actually a string before assigning it to std::string const format. If "format" is present but is not a string (e.g., a boolean or an integer), the implicit conversion to std::string will throw a nlohmann::json::type_error exception.

    if (current->contains("format") && (*current)["format"].is_string()) {
      std::string const format = (*current)["format"];
      if (absl::StartsWith(format, "google.protobuf.")) {
        types_to_import.insert(format);
        // This node is resolved as a protobuf message (e.g. google.protobuf.Any
        // on Status.details items), so do not traverse into its internal
        // additionalProperties or properties.
        continue;
      
    }

generator/internal/discovery_type_vertex.cc (48-56)

medium

If field_json["items"] is not an object, calling .value("type", "") on it will throw a nlohmann::json::type_error exception. We should ensure field_json["items"] is an object before attempting to retrieve its "type".

bool IsStringOrBytes(nlohmann::json const& field_json) {
  std::string const type = field_json.value("type", "");
  if (type == "string" || type == "bytes") return true;
  if (type == "array" && field_json.contains("items") &&
      field_json["items"].is_object()) {
    std::string const item_type = field_json["items"].value("type", "");
    if (item_type == "string" || item_type == "bytes") return true;
  }
  return false;
}

generator/internal/discovery_type_vertex.cc (124-132)

medium

Ensure that v["format"] is actually a string before assigning it to type to prevent a potential nlohmann::json::type_error exception.

  if (type == "any") {
    if (v.contains("format") && v["format"].is_string()) {
      type = v["format"];
    } else {
      type = "google.protobuf.Value";
    }
    return TypeInfo{type, compare_package_name, properties_for_synthesis, false,
                    false};
  }

generator/internal/discovery_type_vertex.cc (165-174)

medium

Ensure that additional_properties and v are objects and their "format" fields are strings before assigning them to map_type to prevent a potential nlohmann::json::type_error exception.

      } else if (map_type == "any") {
        if (additional_properties.is_object() &&
            additional_properties.contains("format") &&
            additional_properties["format"].is_string()) {
          map_type = additional_properties["format"];
        } else if (v.contains("format") && v["format"].is_string()) {
          map_type = v["format"];
        } else {
          map_type = "google.protobuf.Struct";
        }
        return TypeInfo{map_type, compare_package_name,
                        properties_for_synthesis, true, is_message};

generator/internal/discovery_type_vertex.cc (208-228)

medium

Ensure that items and items["additionalProperties"] are objects and their "format" fields are strings before querying them or assigning them to type to prevent potential nlohmann::json::type_error exceptions.

      } else if (type == "any") {
        if (items.is_object() && items.contains("format") &&
            items["format"].is_string()) {
          type = items["format"];
        } else {
          type = "google.protobuf.Value";
        }
        return TypeInfo{type, compare_package_name, nullptr, false, false};
      } else if (type == "object" && items.is_object() &&
                 items.contains("properties")) {
        // Synthesize a nested type for this array.
        type = CapitalizeFirstLetter(field_name + "Item");
        return TypeInfo{type, compare_package_name, &items, false, true};
      } else if (type == "object" && items.is_object() &&
                 items.contains("additionalProperties") &&
                 items["additionalProperties"].is_object() &&
                 items["additionalProperties"].value("type", "") == "any") {
        if (items.contains("format") && items["format"].is_string()) {
          type = items["format"];
        } else if (items["additionalProperties"].contains("format") &&
                   items["additionalProperties"]["format"].is_string()) {
          type = items["additionalProperties"]["format"];
        } else {
          type = "google.protobuf.Struct";
        }
        return TypeInfo{type, compare_package_name, nullptr, false, false};

generator/internal/discovery_type_vertex.cc (510-514)

medium

The current substring check absl::StrContains(field_name, "key") is highly prone to false positives and will over-redact fields that happen to contain "key" as a substring but are not actual keys (e.g., monkey, keyboard, hockey, keypad, keynote).

Since field_name is already formatted as snake_case by the generator, we can perform a precise word-boundary check to ensure "key" is matched only as a standalone word (e.g., kms_key_name, raw_key, rsa_encrypted_key, or key itself). This also eliminates the need to check json_field_name separately.

You can add a helper function ContainsKeyWord in the anonymous namespace:

bool ContainsKeyWord(std::string_view s) {
  for (std::size_t pos = s.find("key"); pos != std::string_view::npos;
       pos = s.find("key", pos + 1)) {
    bool const prefix_ok = (pos == 0 || s[pos - 1] == '_');
    bool const suffix_ok = (pos + 3 == s.size() || s[pos + 3] == '_');
    if (prefix_ok && suffix_ok) return true;
  }
  return false;
}
  if (IsStringOrBytes(field_json) && ContainsKeyWord(field_name)) {
    field_options.emplace_back("debug_redact", "true");
  }
References
  1. Prefer using std::string_view instead of absl::string_view in C++ code. (link)

}

if (current->contains("format")) {
if (current->contains("format") && (*current)["format"].is_string()) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the discovery document contains a format field that is not a string, this is a fundamental problem that the generator cannot recover from. We should emit an error and exit.

auto const& items = (*current)["items"];
if (items.contains("type") && items["type"] == "object" &&
items.contains("additionalProperties") &&
if (items.is_object() && items.contains("type") &&

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, if this isn't an object, then the discovery document is invalid. Emit an error and exit.

Same below. If checks to verify the structure of the document fail, these are non-recoverable.

return false;
}

bool ContainsKeyWord(std::string_view s) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer using string matching functions from absl (e.g. StrContains) and the absl Suffix/Prefix functions instead of std::string::find and iterators directly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants